package com.chen.base.utils.file;

import lombok.SneakyThrows;
import lombok.experimental.UtilityClass;
import lombok.extern.slf4j.Slf4j;

import java.io.*;
import java.nio.charset.StandardCharsets;
import java.util.zip.Deflater;
import java.util.zip.Inflater;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

/**
 * 压缩包工具类
 * @author ChenJinLong
 */
@UtilityClass
@Slf4j
public class ZipUtil {
	
	private static final int BUFFER_SIZE = 2 * 1024;
	
	/**
	 * 压缩成ZIP 方法1
	 * @param srcDir 压缩文件夹路径 
	 * @param out    压缩文件输出流
	 * @throws RuntimeException 压缩失败会抛出运行时异常
	 */
	public void toZip(String srcDir, OutputStream out) throws RuntimeException {
		long start = System.currentTimeMillis();
		try (ZipOutputStream zos = new ZipOutputStream(out)){
			File sourceFile = new File(srcDir);
			compress(sourceFile, zos, sourceFile.getName(), true);
			long end = System.currentTimeMillis();
			log.info("压缩完成, 耗时: " + (end - start) +" ms");
		} catch (Exception e) {
			throw new RuntimeException("zip error from ZipUtils", e);
		}
	}
	
	/**
	 * 递归压缩方法
	 * @param sourceFile 源文件
	 * @param zos		 zip输出流
	 * @param name		 压缩后的名称
	 * @param keepDirStructure  是否保留原来的目录结构,true:保留目录结构;
	 * 							false:所有文件跑到压缩包根目录下(注意：不保留目录结构可能会出现同名文件,会压缩失败)
	 * @throws Exception e
	 */
	private void compress(File sourceFile, ZipOutputStream zos, String name, boolean keepDirStructure) throws Exception{
		byte[] buf = new byte[BUFFER_SIZE];
		if(sourceFile.isFile()){
			// 向zip输出流中添加一个zip实体，构造器中name为zip实体的文件的名字
			zos.putNextEntry(new ZipEntry(name));
			// copy文件到zip输出流中
			int len;
			FileInputStream in = new FileInputStream(sourceFile);
			while ((len = in.read(buf)) != -1){
				zos.write(buf, 0, len);
			}
			// Complete the entry
			zos.closeEntry();
			in.close();
		} else {
			File[] listFiles = sourceFile.listFiles();
			if(listFiles == null || listFiles.length == 0){
				// 需要保留原来的文件结构时,需要对空文件夹进行处理
				if(keepDirStructure){
					// 空文件夹的处理
					zos.putNextEntry(new ZipEntry(name + "/"));
					// 没有文件，不需要文件的copy
					zos.closeEntry();
				}
				
			}else {
				for (File file : listFiles) {
					// 判断是否需要保留原来的文件结构
					if (keepDirStructure) {
						// 注意：file.getName()前面需要带上父文件夹的名字加一斜杠,
						// 不然最后压缩包中就不能保留原来的文件结构,即：所有文件都跑到压缩包根目录下了
						compress(file, zos, name + "/" + file.getName(), keepDirStructure);
					} else {
						compress(file, zos, file.getName(), keepDirStructure);
					}
				}
			}
		}
	}

	@SneakyThrows
	public static byte[] byteZip(byte[] data) {
		Deflater deflater = new Deflater(Deflater.BEST_COMPRESSION);

        try (ByteArrayOutputStream os = new ByteArrayOutputStream(IoFileUtil.DEFAULT_BYTE_SIZE)) {
            deflater.setInput(data);
            deflater.finish();
            byte[] bytes = new byte[BUFFER_SIZE];
            while (!deflater.finished()) {
                int length = deflater.deflate(bytes);
                os.write(bytes, 0, length);
            }

            return os.toByteArray();
        } catch (Exception ex) {
            log.error("字节压缩异常: ", ex);
        } finally {
            deflater.end();
        }
		return new byte[0];
	}

	@SneakyThrows
	public static byte[] unByteZip(byte[] data) {
		Inflater inflater = new Inflater();
        try (ByteArrayOutputStream os = new ByteArrayOutputStream(IoFileUtil.DEFAULT_BYTE_SIZE)) {
            inflater.setInput(data);
            byte[] bytes = new byte[BUFFER_SIZE];
            while (!inflater.finished()) {
                int length = inflater.inflate(bytes);
                os.write(bytes, 0, length);
            }

            return os.toByteArray();
        } catch (Exception ex) {
            log.error("字节解压异常: ", ex);
        } finally {
            inflater.end();
        }
		return new byte[0];
	}

	public static void main(String[] args) throws Exception {
		String str = "3. java，在内存中对字节数组压缩的原理是什么？\n" +
				"在内存中对字节数组进行压缩的原理与文件存储或者网络传输时的压缩原理是相同的。压缩算法会找出数据中的冗余信息并去除，然后将剩余的数据编码成更小的形式。这样就能减小数据的大小，节省空间或者带宽。\n" +
				"\n" +
				"在Java中，可以使用java.util.zip包中的类来实现字节数组的压缩。这些类使用的是DEFLATE压缩算法，这是一种常用的数据压缩算法，能够有效地压缩大多数类型的数据。\n" +
				"————————————————\n" +
				"版权声明：本文为CSDN博主「浪浪的程序猿」的原创文章，遵循CC 4.0 BY-SA版权协议，转载请附上原文出处链接及本声明。\n" +
				"原文链接：https://blog.csdn.net/u013168615/article/details/128602048";
		byte[] b1 = str.getBytes();
		System.out.println("b1 - " + b1.length);
		byte[] b2 = byteZip(b1);
		System.out.println("b2 - " + b2.length);
		byte[] b3 = unByteZip(b2);
		System.out.println("b3 - " + b3.length);
		System.err.println(new String(b2, StandardCharsets.UTF_8));
		System.err.println(new String(b3, StandardCharsets.UTF_8));
	}
}
